package repo

import (
	"encoding/hex"
	"fmt"
	"hash"
	"io"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"
)

var DEVREPO = ""
var PKGREPO = ""

const (
	INSTALLED_STATUS_FILE = ".pkgr-installed"
	VERSIONED_STATUS_FILE = "VERSION"
	INSTALL_SCRIPT_NAME   = "install.sh"
	RECYCLE_NUM_MAX       = 3
)

var (
	ModuleNamePattern          = regexp.MustCompile("^[a-zA-Z0-9_.-]+$")
	ModuleVersionPattern       = regexp.MustCompile("^[a-zA-Z0-9_+-]+$")
	InstallPlanFileNamePattern = regexp.MustCompile("^[a-zA-Z0-9._-]+$")
)

func init() {
	DEVREPO = os.Getenv("DEVREPO")
	PKGREPO = os.Getenv("PKGREPO")
}

func WriteInstalledStatusFile(moduleName string, moduleVersion string) error {
	installRecord := ""

	// 先确定几个关键文件的名字
	installedStatusFile := filepath.Join(DEVREPO, moduleName, moduleVersion, INSTALLED_STATUS_FILE)
	installedStatusFileWriting := installedStatusFile + "-writing"

	// 先删除 writing 文件
	if PathIsFile(installedStatusFileWriting) {
		os.Remove(installedStatusFileWriting)
	}

	// 再删除目标文件
	if PathIsFile(installedStatusFile) {
		os.Remove(installedStatusFile)
	}

	// 将信息写入文件
	err := os.WriteFile(installedStatusFileWriting, []byte(installRecord), 0640)

	// 如果写入失败,那么先尝试做一下清理,如果清理也失败了，也不需要关注
	if err != nil {
		if PathIsFile(installedStatusFileWriting) {
			os.Remove(installedStatusFileWriting)
		}

		return err
	}

	// 如果写入成功，那么尝试改名
	os.Rename(installedStatusFileWriting, installedStatusFile)

	return err
}

// IsInstalled 用于判断是否已经安装
func IsInstalled(moduleName string, moduleVersion string) bool {
	return PathIsFile(path.Join(DEVREPO, moduleName, moduleVersion, INSTALLED_STATUS_FILE))
}

// IsInstalledLabel 用于判断是否已经安装
func IsInstalledLabel(moduleLabel string) bool {
	return PathIsFile(path.Join(DEVREPO, moduleLabel, INSTALLED_STATUS_FILE))
}

// ResolveInstallScript 用于根据模块名和版本号确定安装脚本的路径.
//
// * 如果 DEVREPO/<MODULE_NAME> 下有有 install.sh, 那么调用 install.sh <MODULE_VERSION> 该安装脚本进行安装
// * 如果 PKGREPO/<MODULE_NAME>/<MODULE_VERSION> 下有有 install.sh, 那么调用 install.sh <MODULE_VERSION> 该安装脚本进行安装
// * 如果 PKGREPO/<MODULE_NAME> 下有 install.sh, 那么调用 install.sh <MODULE_VERSION> 该安装脚本进行安装
func ResolveInstallScript(label *ModuleLabel) (string, error) {
	installScript := path.Join(DEVREPO, label.ModuleName, INSTALL_SCRIPT_NAME)
	if PathIsFile(installScript) {
		return installScript, nil
	}

	installScript = path.Join(PKGREPO, label.ModuleName, label.ModuleVersion, INSTALL_SCRIPT_NAME)
	if PathIsFile(installScript) {
		return installScript, nil
	}

	installScript = path.Join(PKGREPO, label.ModuleName, INSTALL_SCRIPT_NAME)
	if PathIsFile(installScript) {
		return installScript, nil
	}

	return "", fmt.Errorf("can not resolve the location of the build script file for `%v'", label.String())
}

// ListRecycles 用于列出匹配指定
func ListRecycles(moduleName string, moduleVersion string) ([]string, error) {
	recycleList := make([]string, 0, 3)

	// 如果回收目录都不存在
	recycleRoot := path.Join(DEVREPO, ".recycle")
	if !PathIsExists(recycleRoot) {
		return recycleList, nil
	}

	// 读取回收根目录下的所有目录名
	moduleDirs, err := os.ReadDir(recycleRoot)
	if err != nil {
		return nil, fmt.Errorf("%v", err.Error())
	}

	// 根据模块名和版本确定该模块的回收前缀
	recyclePrefix := fmt.Sprintf("recycle-%v@%v@", moduleName, moduleVersion)

	// 遍历模块目录
	for _, moduleDir := range moduleDirs {
		// 如果不是目录就跳过
		if !moduleDir.IsDir() {
			continue
		}

		// 检查一下是否匹配特定前缀
		if !strings.HasPrefix(moduleDir.Name(), recyclePrefix) {
			continue
		}

		// 保存模块版本
		recycleList = append(recycleList, moduleDir.Name())
	}

	// 排序（自动从小到大）
	sort.Strings(recycleList)

	return recycleList, nil
}

// MoveInstalledToRecycle 用于将指定的模块搬移道回收区
func MoveInstalledToRecycle(moduleName string, moduleVersion string) error {
	// 如果 .recycle 不存在就尝试创建一个
	recycleRoot := path.Join(DEVREPO, ".recycle")
	if !PathIsDir(recycleRoot) {
		os.MkdirAll(recycleRoot, 0750)
		if !PathIsDir(recycleRoot) {
			return fmt.Errorf("create the recycle directory failed: '%v'", recycleRoot)
		}
	}

	// 找出该模块和版本的所有回收项
	recycleItems, err := ListRecycles(moduleName, moduleVersion)
	if err != nil {
		return err
	}

	// 如果回收项太多就删掉最老的
	if len(recycleItems) >= RECYCLE_NUM_MAX {
		for _, item := range recycleItems[:len(recycleItems)-(RECYCLE_NUM_MAX-1)] {
			os.RemoveAll(path.Join(DEVREPO, ".recycle", item))
		}
	}

	// 确定几个关键的目录
	recycleTime := time.Now().Format("20060102150405")
	recycleName := fmt.Sprintf("recycle-%v@%v@%v", moduleName, moduleVersion, recycleTime)
	moduleInstallDir := path.Join(DEVREPO, moduleName, moduleVersion)
	moduleRecycleDir := path.Join(DEVREPO, ".recycle", recycleName)

	// 尽可能将目标目录删掉依稀
	if PathIsDir(moduleRecycleDir) {
		os.Remove(moduleRecycleDir)
	}

	// 如果模块安装目录存在，那么执行改名（等价于 mv）
	if PathIsDir(moduleInstallDir) {
		err = os.Rename(moduleInstallDir, moduleRecycleDir)
		if err != nil {
			return err
		}
	}

	return nil
}

func DownloadFile(modverDir string, packageName string, downloadURL string) error {
	wget, err := exec.LookPath("wget")
	if err != nil {
		return fmt.Errorf("can not found the 'wget'")
	}

	// 目标包文件
	targetPackageFile := path.Join(modverDir, packageName)

	// 清理下载缓存
	downloadOKFile := path.Join(modverDir, "."+packageName+".download-ok")
	if PathIsFile(downloadOKFile) {
		err := os.Remove(downloadOKFile)
		if err != nil {
			return fmt.Errorf("try to remove the old cache file failed: '%v'", downloadOKFile)
		}
	}

	// 清理下载缓存
	downloadCacheFile := path.Join(modverDir, "."+packageName+".download-cache")
	if PathIsExists(downloadCacheFile) {
		err := os.Remove(downloadCacheFile)
		if err != nil {
			return fmt.Errorf("try to remove the old cache file failed: '%v'", downloadCacheFile)
		}
	}

	// 清理下载备份文件
	downloadBackupFile := path.Join(modverDir, "."+packageName+".download-backup")
	if PathIsFile(downloadBackupFile) {
		err := os.Remove(downloadBackupFile)
		if err != nil {
			return fmt.Errorf("try to remove the old cache file failed: '%v'", downloadBackupFile)
		}
	}

	// 启动 wget 进行下载
	cmd := exec.Command(wget, downloadURL /*"--show-progress", "--progress=bar",*/, "-O", downloadCacheFile)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("download faild: '%v' <- '%v'", downloadCacheFile, downloadURL)
	}

	// 检查下载的文件是否存在
	if !PathIsFile(downloadCacheFile) {
		return fmt.Errorf("download faild, the cache file is missing: '%v' <- '%v'", downloadCacheFile, downloadURL)
	}

	// 将 .xxx.downloading 改名为 .xxx.download-ok
	err = os.Rename(downloadCacheFile, downloadOKFile)
	if err != nil {
		return fmt.Errorf("try to frozen the download file failed: '%v' -> '%v'", downloadCacheFile, downloadOKFile)
	}

	// 备份目标包文件
	if PathIsFile(targetPackageFile) {
		err := os.Rename(targetPackageFile, downloadBackupFile)
		if err != nil {
			return fmt.Errorf("try to backup old file failed: '%v' -> '%v'", targetPackageFile, downloadBackupFile)
		}
	}

	// 下载好的文件生成目标包
	err = os.Rename(downloadOKFile, targetPackageFile)
	if err != nil {
		// 如果备份文件存在就还原备份文件
		if PathIsFile(downloadBackupFile) {
			os.Rename(downloadBackupFile, targetPackageFile)
			// 如果还原失败就没法处理了
		}
		return fmt.Errorf("save the target file failed: '%v' -> '%v'", targetPackageFile, downloadBackupFile)
	}

	// 如果下载成功就删除备份文件
	os.Remove(downloadBackupFile)

	return nil
}

func GenFileHash(hash hash.Hash, filePath string) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}

	defer file.Close()

	hash.Reset()

	_, err = io.Copy(hash, file)
	if err != nil {
		return "", err
	}

	return hex.EncodeToString(hash.Sum(nil)), nil
}

func Install(labelString string) error {
	if !PathIsDir(DEVREPO) {
		return fmt.Errorf("the environment variable `DEVREPO' is missing")
	}

	if !PathIsDir(PKGREPO) {
		return fmt.Errorf("the environment variable `PKGREPO' is missing")
	}

	// 校验模块和版本号
	moduleLabel, err := ParseModuleLabel(labelString)
	if err != nil {
		return fmt.Errorf("invalid module label `%v': %v", labelString, err.Error())
	}

	// 如果已经安装了，并请不需要强制安装，那么可以直接调过
	if IsInstalled(moduleLabel.ModuleName, moduleLabel.ModuleVersion) {
		return fmt.Errorf("module `%v' has been installed", labelString)
	}

	// 定位构建脚本位置
	buildScript, err := ResolveInstallScript(moduleLabel)
	if err != nil {
		return fmt.Errorf("unable to locate the installation script for `%v': %v", moduleLabel, err.Error())
	}

	// 找到 sh 进程
	sh, err := exec.LookPath("sh")
	if err != nil {
		return fmt.Errorf("unable to locate the application of `sh': %v", err.Error())
	}

	// 执行脚本： sh xxx/build.sh install <module_name> <module_version>
	shcmd := exec.Command(sh, "-x", buildScript, "install", labelString)

	// 将命令的输出绑定到本进程的输出
	shcmd.Stdout = os.Stdout
	shcmd.Stderr = os.Stderr

	// 启动命令
	err = shcmd.Run()
	if err != nil {
		return fmt.Errorf("install module `%v' failed: %v", moduleLabel.String(), err.Error())
	}

	// 检查脚本的返回值
	exitCode := shcmd.ProcessState.ExitCode()
	if exitCode != 0 {
		return fmt.Errorf("install module `%v' failed(%v)", moduleLabel.String(), exitCode)
	}

	err = WriteInstalledStatusFile(moduleLabel.ModuleName, moduleLabel.ModuleVersion)
	if err != nil {
		return fmt.Errorf("write installed status file for `%v' failed: %v", moduleLabel.String(), err.Error())
	}

	return nil
}
